[category]: <> (LoRa)
[date]: <> (2021/11/02)
[title]: <> (Using LoRa with Kotlin)

<img src="../../../../images/lora_kotlin.svg" class="responsive" alt="The Kotlin mascot holding a LoRa radio">

##Overview
LoRa is a useful technology for efficiently sending small packets of data long distances without relying on infrastructure like the internet. Many of the currently available LoRa development boards allow for programming using the Arduino IDE, a useful tool for iterating on simple, low-level software. However, in some cases, LoRa applications may need more computing power, or the need to interface with more complicated business logic, like a Java or Kotlin application. 

The solution I'm proposing for using LoRa with Kotlin is to open up the LoRa radio's serial connection, and forward all packets to and from the LoRa device through this serial port. This guide covers how I achieved this and how you can use this approach to add LoRa messaging to your Java or Kotlin project. 

##Getting Started
This guide uses a [Heltec ESP32 LoRa V2](https://heltec.org/project/wifi-lora-32/) board, and a Linux computer. If you have a different Arduino-compatible LoRa board, please feel free to reach out and I can help you get the Arduino code working on your hardware. 
I am also working on getting an Android phone so I can port the Kotlin code to Android as well.

###Install Arduino IDE and needed dependencies
- This guide uses Arduino IDE 1.8 which can be downloaded at <https://arduino.cc>
- Next, follow Heltec's guide on downloading the [proper library](https://heltec-automation-docs.readthedocs.io/en/latest/esp32/quick_start.html).
- Plug in your LoRa board using a micro-USB cable. Under `Tools -> Ports`, select the serial port you just connected. For me this port was `/dev/ttyUSB0`.
- Now you should be able to open the [LoRa Arduino code I wrote](https://notabug.org/paul-lorenc/lora-rose/src/master/rose-device/lora/lora.ino), and compile and upload the code to your LoRa board. This is done by hitting the "Upload" button in the top left of the IDE. 
- If you run into permission errors during upload, please try [this guide](https://www.arduino.cc/en/guide/linux#toc6)

##Understanding the Arduino Code
Arduino code is split into two system functions which are called by the board. There is a `setup()` function which is executed once when the board powers on, then a `loop()` function which is called continuously as the board is running.

The GPL licensed code used in this guide is available on the project's [git repo](https://notabug.org/paul-lorenc/lora-rose)

###Setup
```cpp
void setup()
{
  // Heltec setup function
  Heltec.begin(false /*DisplayEnable Enable*/, true /*Heltec.Heltec.Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);

  // This opens the Serial port on the LoRa board, 
  // then sets the Serial buffer size 
  // (not sure if the buffer size function works, 
  // haven't noticed any performance change) 
  Serial.begin(115200);
  Serial.setRxBufferSize(1024);

  // Sets the LoRa signal bandwidth. 
  // This bandwith along with the default spreading factor of 7,
  // allows for us to send 255 byte packages fairly far distances. 
  // In the future, other signalBandwidth and spreading factors configs,
  // could allow for sending smaller packets of around 50bytes, much longer distances 
  LoRa.setSignalBandwidth(250E3);
}
```

###Loop and Receive Function
```cpp
// Reads LoRa buffer, then prints buffer to serial output
void cbk(int packetSize) {
  packet ="";
  packSize = String(packetSize,DEC);
  for (int i = 0; i < packetSize; i++) { packet += (char) LoRa.read(); }
  Serial.println(packet);
}

void loop()
{ 
  // If the serial buffer is not empty, goto next line
  if(Serial.available() > 0) {
    String data = "";
    // Parse incoming serial data until a newline char
    data = Serial.readStringUntil('\n');
    
    // Send packet
    LoRa.beginPacket(); 
    LoRa.print(data);
    LoRa.endPacket();
    // Set radio back to receive mode
    LoRa.receive();
    
  } 
  // If there is a packet in LoRa buffer, parsePacket() returns > 0
  int packetSize = LoRa.parsePacket();
  if (packetSize) { cbk(packetSize);  }
}

```
##Reading and Writing LoRa packets in Kotlin
Now that we have uploaded the serial driver to the LoRa device, sending and receiving LoRa packets in Kotlin is as simple as sending and receiving serial data over a serial port. To interface with a computer's serial port, we will be using the Java [JSerialComm library](https://fazecast.github.io/jSerialComm/). 
To add this library to your Kotlin project, you can add the following line to the dependencies{} section of your `build.gradle.kts`:
```kotlin
implementation("com.fazecast:jSerialComm:[2.0.0,3.0.0)")
```
Writing serial data is as easy as opening the correct serial port, setting the correct baud rate, then using the `comPort.write()` method.

There are numerous ways to read from the serial port, including non-blocking and blocking methods shown in the [JSerialComm documentation](https://github.com/Fazecast/jSerialComm/wiki/Usage-Examples). For use in Kotlin, I chose the event-based reading method because I think it pairs nicely with [Kotlin Coroutines and Flows](https://kotlinlang.org/docs/flow.html). To implement an event-based reading strategy using JSerialComm, we define an instance of a `SerialPortMessageListener`:
```kotlin
object SerialListener: SerialPortMessageListener {
    override fun getListeningEvents(): Int {
        return SerialPort.LISTENING_EVENT_DATA_RECEIVED;
    }

    override fun getMessageDelimiter(): ByteArray {
        return byteArrayOf(0x0A.toByte())
    }

    override fun delimiterIndicatesEndOfMessage(): Boolean {
       return true
    }

    override fun serialEvent(event: SerialPortEvent?) {
        val delimitedMessage = event!!.receivedData
        var rawString = String(delimitedMessage).replace("\n", "")
        rawString = strDelim.replace("\r", "")
        GlobalScope.launch(Dispatchers.IO) {
            launch {
                IncomingMessageBus.postMessage(rawString)
            }
        }
    }

}
```
Essentially this is a class that defines a delimiter, for which we use the newline char `0x0A`, and a function `serialEvent()` which gets called when a serial message that has a delimiter byte is parsed. Once the message is recieved and the newline and carriage return chars are removed, the message is posted to a Kotlin flow. The implementation of `IncomingMessageBus` is an object wrapping a basic `MutableSharedFlow`.
```kotlin
object IncomingMessageBus {
    private val _incoming = MutableSharedFlow<String>()
    val incoming = _incoming.asSharedFlow()
    suspend fun postMessage(string: String) {
        _incoming.emit(string)
    }
}
```
The actual Kotlin definition of the `SerialInterface` is straightforward, we open the port the LoRa device is on, set baud rate and connect our `SerialListener`. There is also a `close()` method for good measure.
```kotlin
object SerialInterface {
    private val comPort: SerialPort = SerialPort.getCommPort("/dev/ttyUSB0");
    init {
        comPort.openPort()
        comPort.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 0, 0)
        comPort.baudRate = 115200
        comPort.addDataListener(SerialListener)
    }
    fun write(str: String) {
        val str = str + '\n'
        val bstr = str.toByteArray()
        comPort.writeBytes(bstr, bstr.size.toLong())
    }
    fun close() {
        if(comPort.isOpen) {
            comPort.closePort()
        }
    }
}
```
##Processing LoRa/Serial Messages Using Kotlin Flows
Now that we have set up our `SerialListener` to emit data to our `IncomingMesssageBus`, writing functions that react to message receive events are simple. We simply `collect()` the flow, and new messages will be passed to any function calls we add. 
```kotlin
// Run in a new IO thread
GlobalScope.launch(Dispatchers.IO) {
    // str is the string value of the incoming message
    IncomingMessageBus.incoming.collect { str ->
         doSomethingToMessage(str)
    }
}
```
And that covers it! You should now be able to nicely send and receive data over LoRa. If you have experience with Kotlin and know of any improvements over my proposed solution, please feel free to get in touch! I am still very new to Koltin and its more complicated areas like Coroutines and Flows, and I'm sure there are many improvements to be made. My contact info is linked on the [home page](https://paul-lorenc.com) of this website. I can also be reached via matrix `@paul-lorenc:matrix.org` which I prefer. 

This blog also has an [RSS feed](https://paul-lorenc.com/feed.xml) you can subscribe to. In the future I hope to write more about a LoRa mesh chat app I am building with Compose Multiplatform.
